Start swank using the EQL executable, running the swank server in an ECL thread, and using the main thread for the Qt main event loop.
Wrap every internal EQL function in a macro, which will call the function either directly (if called from GUI/main thread), or, if called from another ECL thread, will wrap the function call in a closure.
This closure will be passed to a queued, blocking Qt function running on the UI thread, which will in turn call the closure.
The crucial part is passing a Lisp closure from an ECL thread to Qt and calling it from C++ on the UI/main thread.
This is trivial in ECL/Qt, since both ECL and Qt use/wrap native C threads, and Qt offers a nice utility with Q_INVOKABLE
.
First let's wrap the actual Lisp function, e.g. (foo x y)
in a closure, so we only need to pass one ECL closure pointer to C++.
No need to pass Lisp arguments to C++, they are in the closure; no return value needed from C++, Lisp return values will be assigned in the closure:
;; in some ECL thread (let (values) (run-on-ui-thread ;; in ECL main/GUI thread (lambda () (setf values (multiple-value-list (foo x y))))) ;; back in some ECL thread (values-list values))
Here the implementation of the ECL function run-on-ui-thread
(embedded in Qt):
cl_object run_on_ui_thread(cl_object closure) // define ECL function { QMetaObject::invokeMethod( object, // any QObject from GUI thread "runOnUiThread", // see Q_INVOKABLE Qt::BlockingQueuedConnection, // blocking for return values Q_ARG(void*, closure)); // 'closure' is just a pointer return Cnil; }
Now the Lisp closure will run on the UI/main thread, and the implementation of the Qt function runOnUiThread
is as simple as:
Q_INVOKABLE void runOnUiThread(void* closure) // note Q_INVOKABLE { cl_funcall(1, (cl_object)closure); // ECL function call }
After introducing a macro qrun*
, and wrapping all EQL functions in it (see "lib/thread-safe.lisp"
), we are done!
(Please note that the above code is a stripped down version, see sources for the actual implementation.)